package ccy.focuslayoutmanager;

import ccy.focuslayoutmanager.animation.ValueAnimator;
import com.ohos.RecyclerContainer;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.Component;

import java.util.ArrayList;
import java.util.List;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import ccy.focuslayoutmanager.utils.LogUtil;
import ccy.focuslayoutmanager.annotations.IntDef;
import ccy.focuslayoutmanager.annotations.FloatRange;
import ccy.focuslayoutmanager.annotations.IntRange;

/**
 * The type Focus layout manager.
 */
public class FocusLayoutManager extends RecyclerContainer.LayoutManager {
    /**
     * The constant TAG.
     */
    public static final String TAG = "FocusLayoutManager";
    /**
     * Stacking direction on the left
     */
    public static final int FOCUS_LEFT = 1;
    /**
     * Stacking direction on the right
     */
    public static final int FOCUS_RIGHT = 2;
    /**
     * Stacking direction is up
     */
    public static final int FOCUS_TOP = 3;
    /**
     * Stacking direction down
     */
    public static final int FOCUS_BOTTOM = 4;

    /**
     * Maximum stackable level
     */
    int maxLayerCount;

    /**
     * Stack direction.
     * If the scrolling direction is horizontal, pass {@link #FOCUS_LEFT} or {@link #//FOCUS_Right}.
     * If you want the scrolling direction to be vertical, pass {@link #FOCUS_TOP} or {@link #FOCUS_BOTTOM}.
     */
    @FocusOrientation
    private int focusOrientation = FOCUS_LEFT;
    /**
     * Offset between stacked views
     */
    private float layerPadding;
    /**
     * Margin between common views
     */
    private float normalViewGap;
    /**
     * Whether to automatically select
     */
    private boolean isAutoSelect;
    /**
     * Change the listening interface.
     */
    private List<TrasitionListener> trasitionListeners;
    /**
     *
     */
    private OnFocusChangeListener onFocusChangeListener;
    /**
     *Horizontal cumulative offset
     */
    private long mHorizontalOffset;
    /**
     * Vertical Accumulated Offset
     */
    private long mVerticalOffset;
    /**
     * Position of the first view visible on the screen
     */
    private int mFirstVisiPos;
    /**
     * Position of the last view visible on the screen
     */
    private int mLastVisiPos;
    /**
     * 一The distance to be moved for a sub-complete focus slide.
     */
    private float onceCompleteScrollLength = -1;
    /**
     * Position of the focus view
     */
    private int focusdPosition = -1;
    /**
     * Auto Select Animation
     */
    private ValueAnimator selectAnimator;
    private long autoSelectMinDuration;
    private long autoSelectMaxDuration;

    /**
     * The interface Focus orientation.
     */
    @IntDef({FOCUS_LEFT, FOCUS_RIGHT, FOCUS_TOP, FOCUS_BOTTOM})
    @Retention(RetentionPolicy.SOURCE)
    public @interface FocusOrientation {
    }

    /**
     * Instantiates a new Focus layout manager.
     */
    public FocusLayoutManager() {
        this(new Builder());
    }

    private FocusLayoutManager(Builder builder) {
        this.maxLayerCount = builder.maxLayerCount;
        this.focusOrientation = builder.focusOrientation;
        this.layerPadding = builder.layerPadding;
        this.trasitionListeners = builder.trasitionListeners;
        this.normalViewGap = builder.normalViewGap;
        this.isAutoSelect = builder.isAutoSelect;
        this.onFocusChangeListener = builder.onFocusChangeListener;
        this.autoSelectMinDuration = builder.autoSelectMinDuration;
        this.autoSelectMaxDuration = builder.autoSelectMaxDuration;
    }

    @Override
    public RecyclerContainer.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerContainer.LayoutParams(RecyclerContainer.LayoutParams.MATCH_CONTENT,
                RecyclerContainer.LayoutParams.MATCH_CONTENT);
    }

    @Override
    public void layoutChildren(RecyclerContainer.Adapter adapter, RecyclerContainer.Recycler recycler) {
        if(adapter.getItemCount() == 0){
            removeAndRecycleAllViews(recycler);
            return;
        }

        onceCompleteScrollLength = -1;
        //Detach all existing views and place them in the temporary cache.
        detachAndScrapAttachedViews(recycler);
        fill(recycler, adapter, 0);
    }

    @Override
    public boolean canScrollHorizontally() {
        return focusOrientation == FOCUS_LEFT || focusOrientation == FOCUS_RIGHT;
    }

    @Override
    public boolean canScrollVertically() {
        return focusOrientation == FOCUS_TOP || focusOrientation == FOCUS_BOTTOM;
    }

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerContainer.Adapter adapter, RecyclerContainer.Recycler recycler) {
        //When the finger slides from right to left, dx > 0; when the finger slides from left to right, dx < 0;
        //Shift 0. If there is no sub-view, no sub-view is moved.
        if (dx == 0 || getRecyclerView().getChildCount() == 0) {
            return 0;
        }

        mHorizontalOffset += dx; //Accumulated Actual Sliding Distance
        dx = fill(recycler, adapter, dx);

        return dx;
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerContainer.Adapter adapter, RecyclerContainer.Recycler recycler) {
        //Shift 0. If there is no sub-view, no sub-view is moved.
        if (dy == 0 || getRecyclerView().getChildCount() == 0) {
            return 0;
        }

        mVerticalOffset += dy; //Accumulated Actual Sliding Distance
        dy = fill(recycler, adapter, dy);

        return dy;
    }

    @Override
    public void onDetachedFromWindow() {
        cancelAnimator();
        super.onDetachedFromWindow();
    }

    /**
     * select the view based on the focusOrientation
     * @param recycler
     * @param adapter
     * @param delta
     * @return resultDelta
     */
    private int fill(RecyclerContainer.Recycler recycler, RecyclerContainer.Adapter adapter, int delta) {
        int resultDelta = delta;

        //For readability, there are four methods.
        switch (focusOrientation) {
            case FOCUS_LEFT:
                resultDelta = fillHorizontalLeft(recycler, adapter, delta);
                break;
            case FOCUS_RIGHT:
                resultDelta = fillHorizontalRight(recycler, adapter, delta);
                break;
            case FOCUS_TOP:
                resultDelta = fillVerticalTop(recycler, adapter, delta);
                break;
            case FOCUS_BOTTOM:
                resultDelta = fillVerticalBottom(recycler, adapter, delta);
                break;
            default:
                break;
        }
        recycleChildren(recycler);

        return resultDelta;
    }

    /**
     * Horizontal scrolling, left-stacked layout
     *
     * @param recycler
     * @param adapter
     * @param dx       Offset. When the finger slides from right to left, dx > 0; when the finger slides from left to right, dx < 0;
     * @return dx
     */
    private int fillHorizontalLeft(RecyclerContainer.Recycler recycler, RecyclerContainer.Adapter adapter,
                                   int dx) {

        //----------------1. Boundary detection-----------------
        if (dx < 0) {
            //Left boundary reached
            if (mHorizontalOffset < 0) {
                mHorizontalOffset = dx = 0;
            }
        }

        if (dx > 0) {
            //Slide to the stack view. There is no common view, indicating that you have reached the right boundary.
            if (mLastVisiPos - mFirstVisiPos <= maxLayerCount - 1) {
                //Because I added dx once, I subtracted it back.
                mHorizontalOffset -= dx;
                dx = 0;
            }
        }

        //Detach all views and place them in the temporary cache.
        detachAndScrapAttachedViews(recycler);

        //----------------2. Initialize layout data-----------------

        float startX = getRecyclerView().getPaddingLeft() - layerPadding;

        Component tempView = null;
        int tempPosition = -1;

        if (onceCompleteScrollLength == -1) {
            //Because mFirstVisiPos may be changed below, use tempPosition to temporarily store it.
            tempPosition = mFirstVisiPos;
            tempView = recycler.getViewForPosition(tempPosition);
            measureChildWithMargins(tempView, 0, 0);
            onceCompleteScrollLength = getDecoratedMeasurementHorizontal(tempView) + normalViewGap;
        }
        //Percentage of the progress of a complete focus slide.
        // The percentage increases in the direction of stack movement. (i.e., if Focal_LEFT, the fraction will be moved from 0% to 100% from right to left)
        float fraction =
                (Math.abs(mHorizontalOffset) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);

        //View offset of the stack area. During a complete focus slide, the total offset is a layerPadding distance.
        float layerViewOffset = layerPadding * fraction;
        //View offset of a common area. During a complete focus slide, the total displacement is a onceCompleteScrollLength
        float normalViewOffset = onceCompleteScrollLength * fraction;

        boolean isLayerViewOffsetSetted = false;
        boolean isNormalViewOffsetSetted = false;

        //Fixed the first visible view: mFirstVisiPos. The number of complete onceCompleteScrollLengths is the number of complete onceCompleteScrollLengths.item
        mFirstVisiPos = (int) Math.floor(Math.abs(mHorizontalOffset) / onceCompleteScrollLength); //Round down
        //Temporarily assign the value of mLastVisiPos to getItemCount() - 1.
        // During the following traversing, the system checks whether the view overflows the screen, corrects the value in a timely manner, and ends the layout.
        mLastVisiPos = getRecyclerView().getAdapter().getItemCount() - 1;

        int newFocusedPosition = mFirstVisiPos + maxLayerCount - 1;
        if (newFocusedPosition != focusdPosition) {
            if (onFocusChangeListener != null) {
                onFocusChangeListener.onFocusChanged(newFocusedPosition, focusdPosition);
            }
            focusdPosition = newFocusedPosition;
        }


        //----------------3. Start the layout.-----------------

        for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
            //It belongs to the stacking area.
            if (i - mFirstVisiPos < maxLayerCount) {
                Component item;

                if (i == tempPosition && tempView != null) {
                    //If a temporary view has been obtained during data initialization, don't waste it!
                    item = tempView;
                } else {
                    item = recycler.getViewForPosition(i);
                }
                addView(item);
                measureChildWithMargins(item, 0, 0);

                startX += layerPadding;
                if (!isLayerViewOffsetSetted) {
                    startX -= layerViewOffset;
                    isLayerViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        trasitionListener.handleLayerView(this, item, i - mFirstVisiPos,
                                maxLayerCount, i, fraction, dx);
                    }
                }

                int l, t, r, b;
                l = (int) startX;
                t = getRecyclerView().getPaddingTop();
                r = (int) (startX + getDecoratedMeasurementHorizontal(item));
                b = getRecyclerView().getPaddingTop() + getDecoratedMeasurementVertical(item);
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);
            }
            else {//It belongs to a common area.
                Component item = recycler.getViewForPosition(i);
                addView(item);
                measureChildWithMargins(item, 0, 0);

                startX += onceCompleteScrollLength;
                if (!isNormalViewOffsetSetted) {
                    startX += layerViewOffset;
                    startX -= normalViewOffset;
                    isNormalViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        if (i - mFirstVisiPos == maxLayerCount) {
                            trasitionListener.handleFocusingView(this, item, i, fraction, dx);
                        } else {
                            trasitionListener.handleNormalView(this, item, i, fraction, dx);
                        }
                    }
                }

                int l, t, r, b;
                l = (int) startX;
                t = getRecyclerView().getPaddingTop();
                r = (int) (startX + getDecoratedMeasurementHorizontal(item));
                b = getRecyclerView().getPaddingTop() + getDecoratedMeasurementVertical(item);
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);

                //Check whether the layout position of the next view exceeds the screen. If yes, modify mLastVisiPos and skip traversal.
                if (startX + onceCompleteScrollLength > getRecyclerView().getWidth() - getRecyclerView().getPaddingRight()) {
                    mLastVisiPos = i;
                    break;
                }
            }
        }
        return dx;
    }

    /**
     * Scroll horizontally, stack the layout to the right. For details, see.
     * {@link #fillHorizontalLeft(RecyclerContainer.Recycler, RecyclerContainer.Adapter, int)}。
     *
     * @param recycler
     * @param adapter
     * @param dx       Offset. When the finger slides from right to left, dx > 0; when the finger slides from left to right, dx < 0;
     * @return dx
     */
    private int fillHorizontalRight(RecyclerContainer.Recycler recycler, RecyclerContainer.Adapter adapter,
                                    int dx) {

        //----------------1. Boundary detection-----------------

        //The layout starts from the rightmost, so mHorizontalOffset is always negative.
        if (dx > 0) {
            if (mHorizontalOffset > 0) {
                mHorizontalOffset = dx = 0;
            }
        }
        if (dx < 0) {
            if (mLastVisiPos - mFirstVisiPos <= maxLayerCount - 1) {
                mHorizontalOffset -= dx;
                dx = 0;
            }
        }

        detachAndScrapAttachedViews(recycler);

        //----------------2. Initialize layout data-----------------

        float startX = getRecyclerView().getWidth() - getRecyclerView().getPaddingRight() + layerPadding;

        Component tempView = null;
        int tempPosition = -1;

        if (onceCompleteScrollLength == -1) {
            //Because mFirstVisiPos may be changed below, use tempPosition to temporarily store it.
            tempPosition = mFirstVisiPos;
            tempView = recycler.getViewForPosition(tempPosition);
            measureChildWithMargins(tempView, 0, 0);
            onceCompleteScrollLength = getDecoratedMeasurementHorizontal(tempView) + normalViewGap;
        }

        //Percentage of the progress of a complete focus slide. The percentage increases in the direction of stack movement.
        // (i.e., if Focal_LEFT, the fraction will be moved from 0% to 100% from right to left)
        float fraction =
                (Math.abs(mHorizontalOffset) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);

        //View offset of the stack area. During a complete focus slide, the total offset is a layerPadding distance.
        float layerViewOffset = layerPadding * fraction;
        //View offset of a common area. During a complete focus slide, the total displacement is a onceCompleteScrollLength
        float normalViewOffset = onceCompleteScrollLength * fraction;
        boolean isLayerViewOffsetSetted = false;
        boolean isNormalViewOffsetSetted = false;

        //Fixed the first visible view: mFirstVisiPos. The number of complete onceCompleteScrollLengths that have been slided indicates the number of items that have been slided.
        mFirstVisiPos = (int) Math.floor(Math.abs(mHorizontalOffset) / onceCompleteScrollLength); //Round down

        //Temporarily assign the value of mLastVisiPos to getItemCount() - 1.
        // During the following traversing, the system checks whether the view overflows the screen, corrects the value in a timely manner, and ends the layout.
        mLastVisiPos = getRecyclerView().getAdapter().getItemCount() - 1;

        int newFocusedPosition = mFirstVisiPos + maxLayerCount - 1;
        if (newFocusedPosition != focusdPosition) {
            if (onFocusChangeListener != null) {
                onFocusChangeListener.onFocusChanged(newFocusedPosition, focusdPosition);
            }
            focusdPosition = newFocusedPosition;
        }


        //----------------3. Start the layout.-----------------

        for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
            //It belongs to the stacking area.
            if (i - mFirstVisiPos < maxLayerCount) {
                Component item;

                if (i == tempPosition && tempView != null) {
                    //If a temporary view has been obtained during data initialization, don't waste it!
                    item = tempView;
                } else {
                    item = recycler.getViewForPosition(i);
                }
                addView(item);
                measureChildWithMargins(item, 0, 0);

                startX -= layerPadding;
                if (!isLayerViewOffsetSetted) {
                    startX += layerViewOffset;
                    isLayerViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        trasitionListener.handleLayerView(this, item, i - mFirstVisiPos,
                                maxLayerCount, i, fraction, dx);
                    }
                }

                int l, t, r, b;
                l = (int) (startX - getDecoratedMeasurementHorizontal(item));
                t = getRecyclerView().getPaddingTop();
                r = (int) (startX);
                b = getRecyclerView().getPaddingTop() + getDecoratedMeasurementVertical(item);
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);
            }
            else {//It belongs to a common area.
                Component item = recycler.getViewForPosition(i);
                addView(item);
                measureChildWithMargins(item, 0, 0);
                startX -= onceCompleteScrollLength;
                if (!isNormalViewOffsetSetted) {
                    startX -= layerViewOffset;
                    startX += normalViewOffset;
                    isNormalViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        if (i - mFirstVisiPos == maxLayerCount) {
                            trasitionListener.handleFocusingView(this, item, i, fraction, dx);
                        } else {
                            trasitionListener.handleNormalView(this, item, i, fraction, dx);
                        }
                    }
                }

                int l, t, r, b;
                l = (int) (startX - getDecoratedMeasurementHorizontal(item));
                t = getRecyclerView().getPaddingTop();
                r = (int) (startX);
                b = getRecyclerView().getPaddingTop() + getDecoratedMeasurementVertical(item);
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);

                //Check whether the layout position of the next view exceeds the screen. If yes, modify mLastVisiPos and skip traversal.
                if (startX - onceCompleteScrollLength < getRecyclerView().getPaddingLeft()) {
                    mLastVisiPos = i;
                    break;
                }
            }
        }
        return dx;
    }

    /**
     * Vertical scrolling, stacking up layout
     * For details, see.{@link #fillHorizontalLeft(RecyclerContainer.Recycler, RecyclerContainer.Adapter, int)}。
     *
     * @param recycler
     * @param adapter
     * @param dy       Offset. dy < 0 when the finger slides from top to bottom; dy > 0 when the finger slides from bottom to top;
     * @return dy
     */
    private int fillVerticalTop(RecyclerContainer.Recycler recycler, RecyclerContainer.Adapter adapter,
                                int dy) {

        //----------------1. Boundary detection-----------------
        if (dy < 0) {
            if (mVerticalOffset < 0) {
                mVerticalOffset = dy = 0;
            }
        }

        if (dy > 0) {
            if (mLastVisiPos - mFirstVisiPos <= maxLayerCount - 1) {
                mVerticalOffset -= dy;
                dy = 0;
            }
        }

        detachAndScrapAttachedViews(recycler);

        //----------------2. Initialize layout data-----------------

        float startY = getRecyclerView().getPaddingTop() - layerPadding;

        Component tempView = null;
        int tempPosition = -1;
        if (onceCompleteScrollLength == -1) {
            //Because mFirstVisiPos may be changed below, use tempPosition to temporarily store it.
            tempPosition = mFirstVisiPos;
            tempView = recycler.getViewForPosition(tempPosition);
            measureChildWithMargins(tempView, 0, 0);
            onceCompleteScrollLength = getDecoratedMeasurementVertical(tempView) + normalViewGap;
        }
        //Percentage of the progress of a complete focus slide. The percentage increases in the direction of stack movement. (i.e., if Focal_LEFT, the fraction will be moved from 0% to 100% from right to left)
        float fraction =
                (Math.abs(mVerticalOffset) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);

        //View offset of the stack area. During a complete focus slide, the total offset is a layerPadding distance.
        float layerViewOffset = layerPadding * fraction;
        //View offset of a common area. During a complete focus slide, the total displacement is a onceCompleteScrollLength
        float normalViewOffset = onceCompleteScrollLength * fraction;
        boolean isLayerViewOffsetSetted = false;
        boolean isNormalViewOffsetSetted = false;

        //Fixed the first visible view: mFirstVisiPos. The number of complete onceCompleteScrollLengths that have been slided indicates the number of items that have been slided.
        mFirstVisiPos = (int) Math.floor(Math.abs(mVerticalOffset) / onceCompleteScrollLength);
        //Round down
        //Temporarily assign the value of mLastVisiPos to getItemCount() - 1. During the following traversing, the system checks whether the view overflows the screen, corrects the value in a timely manner, and ends the layout.
        mLastVisiPos = getRecyclerView().getAdapter().getItemCount() - 1;

        int newFocusedPosition = mFirstVisiPos + maxLayerCount - 1;
        if (newFocusedPosition != focusdPosition) {
            if (onFocusChangeListener != null) {
                onFocusChangeListener.onFocusChanged(newFocusedPosition, focusdPosition);
            }
            focusdPosition = newFocusedPosition;
        }

        //----------------3. Start the layout.-----------------

        for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
            //It belongs to the stacking area.
            if (i - mFirstVisiPos < maxLayerCount) {
                Component item;

                if (i == tempPosition && tempView != null) {
                    //If a temporary view has been obtained during data initialization, don't waste it!
                    item = tempView;
                } else {
                    item = recycler.getViewForPosition(i);
                }
                addView(item);
                measureChildWithMargins(item, 0, 0);

                startY += layerPadding;
                if (!isLayerViewOffsetSetted) {
                    startY -= layerViewOffset;
                    isLayerViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        trasitionListener.handleLayerView(this, item, i - mFirstVisiPos,
                                maxLayerCount, i, fraction, dy);
                    }
                }

                int l, t, r, b;
                l = getRecyclerView().getPaddingLeft();
                t = (int) startY;
                r = getRecyclerView().getPaddingLeft() + getDecoratedMeasurementHorizontal(item);
                b = (int) (startY + getDecoratedMeasurementVertical(item));
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);


            }
            else {//It belongs to a common area.
                Component item = recycler.getViewForPosition(i);
                addView(item);
                measureChildWithMargins(item, 0, 0);
                startY += onceCompleteScrollLength;
                if (!isNormalViewOffsetSetted) {
                    startY += layerViewOffset;
                    startY -= normalViewOffset;
                    isNormalViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        if (i - mFirstVisiPos == maxLayerCount) {
                            trasitionListener.handleFocusingView(this, item, i, fraction, dy);
                        } else {
                            trasitionListener.handleNormalView(this, item, i, fraction, dy);
                        }
                    }
                }

                int l, t, r, b;
                l = getRecyclerView().getPaddingLeft();
                t = (int) startY;
                r = getRecyclerView().getPaddingLeft() + getDecoratedMeasurementHorizontal(item);
                b = (int) (startY + getDecoratedMeasurementVertical(item));
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);

                //Check whether the layout position of the next view exceeds the screen. If yes, modify mLastVisiPos and skip traversal.
                if (startY + onceCompleteScrollLength > getRecyclerView().getHeight() - getRecyclerView().getPaddingBottom()) {
                    mLastVisiPos = i;
                    break;
                }
            }
        }
        return dy;
    }

    /**
     * Vertical scrolling, stacking down layout
     *For details, see.{@link #fillHorizontalLeft(RecyclerContainer.Recycler, RecyclerContainer.Adapter, int)}。
     *
     * @param recycler
     * @param adapter
     * @param dy      Offset. dy < 0 when the finger slides from top to bottom; dy > 0 when the finger slides from bottom to top;
     * @return dy
     */
    private int fillVerticalBottom(RecyclerContainer.Recycler recycler, RecyclerContainer.Adapter adapter,
                                   int dy) {

        //----------------1. Boundary detection-----------------
        //The layout starts from the bottom, so mVerticalOffset is always negative.

        if (dy > 0) {
            //The upper boundary has been reached.
            if (mVerticalOffset > 0) {
                mVerticalOffset = dy = 0;
            }
        }

        if (dy < 0) {
            if (mLastVisiPos - mFirstVisiPos <= maxLayerCount - 1) {
                mVerticalOffset -= dy;
                dy = 0;
            }
        }

        detachAndScrapAttachedViews(recycler);

        //----------------2. Initialize layout data-----------------

        float startY = getRecyclerView().getHeight() - getRecyclerView().getPaddingBottom() + layerPadding;

        Component tempView = null;
        int tempPosition = -1;
        if (onceCompleteScrollLength == -1) {
            //Because mFirstVisiPos may be changed below, use tempPosition to temporarily store it.
            tempPosition = mFirstVisiPos;
            tempView = recycler.getViewForPosition(tempPosition);
            measureChildWithMargins(tempView, 0, 0);
            onceCompleteScrollLength = getDecoratedMeasurementVertical(tempView) + normalViewGap;
        }
        //Percentage of the progress of a complete focus slide. The percentage increases in the direction of stack movement. (i.e., if Focal_LEFT, the fraction will be moved from 0% to 100% from right to left)
        float fraction =
                (Math.abs(mVerticalOffset) % onceCompleteScrollLength) / (onceCompleteScrollLength * 1.0f);

        //View offset of the stack area. During a complete focus slide, the total offset is a layerPadding distance.
        float layerViewOffset = layerPadding * fraction;
        //View offset of a common area. During a complete focus slide, the total displacement is a onceCompleteScrollLength
        float normalViewOffset = onceCompleteScrollLength * fraction;
        boolean isLayerViewOffsetSetted = false;
        boolean isNormalViewOffsetSetted = false;

        //Fixed the first visible view: mFirstVisiPos. The number of complete onceCompleteScrollLengths that have been slided indicates the number of items that have been slided.
        mFirstVisiPos = (int) Math.floor(Math.abs(mVerticalOffset) / onceCompleteScrollLength);
        //Round down
        //    Parallel           
        //Temporarily assign the value of mLastVisiPos to getItemCount() - 1. During the following traversing, the system checks whether the view overflows the screen, corrects the value in a timely manner, and ends rounding down the layout.
        mLastVisiPos = getRecyclerView().getAdapter().getItemCount() - 1;

        int newFocusedPosition = mFirstVisiPos + maxLayerCount - 1;
        if (newFocusedPosition != focusdPosition) {
            if (onFocusChangeListener != null) {
                onFocusChangeListener.onFocusChanged(newFocusedPosition, focusdPosition);
            }
            focusdPosition = newFocusedPosition;
        }

        //----------------3. Start the layout.-----------------

        for (int i = mFirstVisiPos; i <= mLastVisiPos; i++) {
            //It belongs to the stacking area.
            if (i - mFirstVisiPos < maxLayerCount) {
                Component item;

                if (i == tempPosition && tempView != null) {
                    //Such as If you have taken a temporary view when initializing the data, don't waste it!
                    item = tempView;
                } else {
                    item = recycler.getViewForPosition(i);
                }
                addView(item);
                measureChildWithMargins(item, 0, 0);

                startY -= layerPadding;
                if (!isLayerViewOffsetSetted) {
                    startY += layerViewOffset;
                    isLayerViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        trasitionListener.handleLayerView(this, item, i - mFirstVisiPos,
                                maxLayerCount, i, fraction, dy);
                    }
                }

                int l, t, r, b;
                l = getRecyclerView().getPaddingLeft();
                t = (int) (startY - getDecoratedMeasurementVertical(item));
                r = getRecyclerView().getPaddingLeft() + getDecoratedMeasurementHorizontal(item);
                b = (int) (startY);
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);


            }
            else {//It belongs to a common area.
                Component item = recycler.getViewForPosition(i);
                addView(item);
                measureChildWithMargins(item, 0, 0);
                startY -= onceCompleteScrollLength;
                if (!isNormalViewOffsetSetted) {
                    startY -= layerViewOffset;
                    startY += normalViewOffset;
                    isNormalViewOffsetSetted = true;
                }

                if (trasitionListeners != null && !trasitionListeners.isEmpty()) {
                    for (TrasitionListener trasitionListener : trasitionListeners) {
                        if (i - mFirstVisiPos == maxLayerCount) {
                            trasitionListener.handleFocusingView(this, item, i, fraction, dy);
                        } else {
                            trasitionListener.handleNormalView(this, item, i, fraction, dy);
                        }
                    }
                }

                int l, t, r, b;
                l = getRecyclerView().getPaddingLeft();
                t = (int) (startY - getDecoratedMeasurementVertical(item));
                r = getRecyclerView().getPaddingLeft() + getDecoratedMeasurementHorizontal(item);
                b = (int) (startY);
                getRecyclerView().layoutDecoratedWithMargins(item, l, t, r, b);

                //Check whether the layout position of the next view exceeds the screen. If yes, modify mLastVisiPos and skip traversal.
                if (startY - onceCompleteScrollLength < getRecyclerView().getPaddingTop()) {
                    mLastVisiPos = i;
                    break;
                }
            }
        }
        return dy;
    }

    @Override
    public void onAdapterChanged(RecyclerContainer.Adapter oldAdapter,
                                 RecyclerContainer.Adapter newAdapter) {
        resetData();
        super.onAdapterChanged(oldAdapter, newAdapter);
    }

    @Override
    public void onMeasure(RecyclerContainer.Recycler recycler, RecyclerContainer.Adapter adapter, int widthSpec, int heightSpec) {
        int widthMode = Component.EstimateSpec.getMode(widthSpec);
        int heightMode = Component.EstimateSpec.getMode(heightSpec);
        if (widthMode == Component.EstimateSpec.NOT_EXCEED && (focusOrientation == FOCUS_LEFT || focusOrientation == FOCUS_RIGHT)) {
            if (BuildConfig.DEBUG) {
                throw new RuntimeException("FocusLayoutManager-onMeasure:when the scrolling direction is horizontal，recyclerView" +
                        "Do not use the width of wrap_content");
            } else {
                LogUtil.error(TAG, "FocusLayoutManager-onMeasure:when the scrolling direction is horizontal，recyclerView" +
                        "Do not use the width of wrap_content");
            }

        }
        if (heightMode == Component.EstimateSpec.NOT_EXCEED && (focusOrientation == FOCUS_TOP || focusOrientation == FOCUS_BOTTOM)) {
            if (BuildConfig.DEBUG) {
                throw new RuntimeException("FocusLayoutManager-onMeasure:When the scrolling direction is vertical，recyclerView" +
                        "Please do not use the height of wrap_content");
            } else {
                LogUtil.error(TAG, "FocusLayoutManager-onMeasure:When the scrolling direction is vertical，recyclerView" +
                        "Please do not use the height of wrap_content");
            }
        }
        super.onMeasure(recycler, adapter, widthSpec, heightSpec);
    }

    @Override
    public boolean isAutoMeasureEnabled() {
        return true;
    }

    /**
     * Recycle the items to be recycled.
     *
     * @param recycler the recycler
     */
    private void recycleChildren(RecyclerContainer.Recycler recycler) {
        List<RecyclerContainer.ViewHolder> scrapList = recycler.getScrapList();

        for (int i = 0; i < scrapList.size(); i++) {
            RecyclerContainer.ViewHolder holder = scrapList.get(i);
            removeAndRecycleView(holder.itemView, recycler);
        }
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        switch (state) {
            case RecyclerContainer.SCROLL_STATE_DRAGGING:
                //Stops the currently playing animation when the finger is pressed
                cancelAnimator();
                break;
            case RecyclerContainer.SCROLL_STATE_IDLE:
                //When the list scrolling stops, check whether the auto-select function is enabled.
                if (isAutoSelect) {
                    //Find the index of the item closest to the target drop point.
                    smoothScrollToPosition(findShouldSelectPosition());
                }
                break;
            default:
                break;
        }
    }

    /**
     * Returns the position that should be selected.
     *
     * @return mFirstVisiPos
     */
    private int findShouldSelectPosition() {
        if (onceCompleteScrollLength == -1 || mFirstVisiPos == -1) {
            return -1;
        }
        int remainder = -1;
        if (focusOrientation == FOCUS_LEFT || focusOrientation == FOCUS_RIGHT) {
            remainder = (int) (Math.abs(mHorizontalOffset) % onceCompleteScrollLength);
        } else if (focusOrientation == FOCUS_TOP || focusOrientation == FOCUS_BOTTOM) {
            remainder = (int) (Math.abs(mVerticalOffset) % onceCompleteScrollLength);
        }

        if (remainder >= onceCompleteScrollLength / 2.0f) { //More than half, next item should be selected
            if (mFirstVisiPos + 1 <= getRecyclerView().getAdapter().getItemCount() - 1) {
                return mFirstVisiPos + 1;
            }
        }
        return mFirstVisiPos;
    }

    /**
     * Returns the offset required to move to position
     *
     * @param position
     * @return position the position
     */
    private float getScrollToPositionOffset(int position) {
        if (focusOrientation == FOCUS_LEFT) {
            return position * onceCompleteScrollLength - Math.abs(mHorizontalOffset);
        } else if (focusOrientation == FOCUS_RIGHT) {
            return -(position * onceCompleteScrollLength - Math.abs(mHorizontalOffset));
        } else if (focusOrientation == FOCUS_TOP) {
            return position * onceCompleteScrollLength - Math.abs(mVerticalOffset);
        } else if (focusOrientation == FOCUS_BOTTOM) {
            return -(position * onceCompleteScrollLength - Math.abs(mVerticalOffset));
        }
        return 0;
    }

    @Override
    public void smoothScrollToPosition(RecyclerContainer recyclerView, RecyclerContainer.Adapter adapter,
                                       int position) {
        smoothScrollToPosition(position);
    }

    @Override
    public void scrollToPosition(int position) {
        if (focusOrientation == FOCUS_LEFT || focusOrientation == FOCUS_RIGHT) {
            mHorizontalOffset += getScrollToPositionOffset(position);
            getRecyclerView().requestLayout();
        } else if (focusOrientation == FOCUS_TOP || focusOrientation == FOCUS_BOTTOM) {
            mVerticalOffset += getScrollToPositionOffset(position);
            getRecyclerView().requestLayout();
        }
    }

    /**
     * Smooth scroll to a position
     *
     * @param position Target item index
     */
    public void smoothScrollToPosition(int position) {
        if (position > -1 && position < getRecyclerView().getAdapter().getItemCount()) {
            startValueAnimator(position);
        }
    }

    private void startValueAnimator(int position) {
        cancelAnimator();
        final float distance = getScrollToPositionOffset(position);

        long minDuration = autoSelectMinDuration;
        long maxDuration = autoSelectMaxDuration;
        long duration;
        float distanceFraction = (Math.abs(distance) / (onceCompleteScrollLength));
        if (distance <= onceCompleteScrollLength) {
            duration = (long) (minDuration + (maxDuration - minDuration) * distanceFraction);
        } else {
            duration = (long) (maxDuration * distanceFraction);
        }

        selectAnimator = ValueAnimator.ofFloat(0.0f, distance);
        selectAnimator.setDuration(duration);
        selectAnimator.setInterpolator(Animator.CurveType.LINEAR);
        final float startedOffset =
                (focusOrientation == FOCUS_LEFT || focusOrientation == FOCUS_RIGHT) ?
                        mHorizontalOffset : mVerticalOffset;

        selectAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                if (focusOrientation == FOCUS_LEFT || focusOrientation == FOCUS_RIGHT) {
                    if (mHorizontalOffset < 0) {
                        mHorizontalOffset =
                                (long) Math.floor(startedOffset + value);
                    } else {
                        mHorizontalOffset =
                                (long) Math.ceil(startedOffset + value);
                    }
                    getRecyclerView().requestLayout();
                } else if (focusOrientation == FOCUS_TOP || focusOrientation == FOCUS_BOTTOM) {
                    if (mVerticalOffset < 0) {
                        mVerticalOffset =
                                (long) Math.floor(startedOffset + value);
                    } else {
                        mVerticalOffset =
                                (long) Math.ceil(startedOffset + value);
                    }
                    getRecyclerView().requestLayout();
                }
            }
        });

        selectAnimator.start();
    }


    /**
     * Obtains the space occupied by a childView in the horizontal direction and takes the margin into account.
     *
     * @param view the view
     * @return decorated measurement horizontal
     */
    public int getDecoratedMeasurementHorizontal(Component view) {
        final RecyclerContainer.LayoutParams params = (RecyclerContainer.LayoutParams)
                view.getLayoutConfig();
        return getDecoratedMeasuredWidth(view) + params.getMarginLeft()
                + params.getMarginRight();
    }

    /**
     * Obtains the space occupied by a childView in the vertical direction and takes the margin into account.
     *
     * @param view the view
     * @return decorated measurement vertical
     */
    public int getDecoratedMeasurementVertical(Component view) {
        final RecyclerContainer.LayoutParams params = (RecyclerContainer.LayoutParams)
                view.getLayoutConfig();
        return getDecoratedMeasuredHeight(view) + params.getMarginTop()
                + params.getMarginBottom();
    }

    /**
     * Gets vertical space.
     *
     * @return the vertical space
     */
    public int getVerticalSpace() {
        return getRecyclerView().getHeight() - getRecyclerView().getPaddingTop() - getRecyclerView().getPaddingBottom();
    }

    /**
     * Gets horizontal space.
     *
     * @return the horizontal space
     */
    public int getHorizontalSpace() {
        return getRecyclerView().getWidth() - getRecyclerView().getPaddingLeft() - getRecyclerView().getPaddingRight();
    }

    /**
     * Reset Data
     */
    public void resetData() {
        mFirstVisiPos = 0;
        mLastVisiPos = 0;
        onceCompleteScrollLength = -1;
        mHorizontalOffset = 0;
        mVerticalOffset = 0;
        focusdPosition = -1;
        cancelAnimator();
    }

    /**
     * Cancel animator.
     */
    public void cancelAnimator() {
        if (selectAnimator != null && selectAnimator.isRunning()) {
            selectAnimator.cancel();
        }
    }

    /**
     * Gets focusd position.
     *
     * @return the focusd position
     */
    public int getFocusdPosition() {
        return focusdPosition;
    }

    /**
     * Sets focusd position.
     *
     * @param focusdPosition the focusd position
     * @param anim           the anim
     */
    public void setFocusdPosition(int focusdPosition, boolean anim) {
        if (focusdPosition > -1 && focusdPosition < getRecyclerView().getAdapter().getItemCount() && focusdPosition >= maxLayerCount - 1) {
            if (anim) {
                smoothScrollToPosition(focusdPosition - (maxLayerCount - 1));
            } else {
                scrollToPosition(focusdPosition - (maxLayerCount - 1));
            }
        }
    }

    /**
     * Is auto select boolean.
     *
     * @return the boolean
     */
    public boolean isAutoSelect() {
        return isAutoSelect;
    }

    /**
     * Sets auto select.
     *
     * @param autoSelect the auto select
     */
    public void setAutoSelect(boolean autoSelect) {
        isAutoSelect = autoSelect;
    }

    /**
     * Gets max layer count.
     *
     * @return the max layer count
     */
    public int getMaxLayerCount() {
        return maxLayerCount;
    }

    /**
     * Sets max layer count.
     *
     * @param maxLayerCount the max layer count
     */
    public void setMaxLayerCount(int maxLayerCount) {
        this.maxLayerCount = maxLayerCount;
        resetData();
        getRecyclerView().requestLayout();
    }

    /**
     * Gets focus orientation.
     *
     * @return the focus orientation
     */
    public int getFocusOrientation() {
        return focusOrientation;
    }

    /**
     * Sets focus orientation.
     *
     * @param focusOrientation the focus orientation
     */
    public void setFocusOrientation(@FocusOrientation int focusOrientation) {
        this.focusOrientation = focusOrientation;
        resetData();
        getRecyclerView().requestLayout();
    }

    /**
     * Gets layer padding.
     *
     * @return the layer padding
     */
    public float getLayerPadding() {
        return layerPadding;
    }

    /**
     * Sets layer padding.
     *
     * @param layerPadding the layer padding
     */
    public void setLayerPadding(float layerPadding) {
        if (layerPadding < 0) {
            layerPadding = 0;
        }
        this.layerPadding = layerPadding;
        resetData();
        getRecyclerView().requestLayout();
    }

    /**
     * Gets normal view gap.
     *
     * @return the normal view gap
     */
    public float getNormalViewGap() {
        return normalViewGap;
    }

    /**
     * Sets normal view gap.
     *
     * @param normalViewGap the normal view gap
     */
    public void setNormalViewGap(float normalViewGap) {
        this.normalViewGap = normalViewGap;
        resetData();
        getRecyclerView().requestLayout();
    }

    /**
     * Gets trasition listeners.
     *
     * @return the trasition listeners
     */
    public List<TrasitionListener> getTrasitionListeners() {
        return trasitionListeners;
    }

    /**
     * Add trasition listener.
     *
     * @param trasitionListener the trasition listener
     */
    public void addTrasitionListener(TrasitionListener trasitionListener) {
        this.trasitionListeners.add(trasitionListener);
        getRecyclerView().requestLayout();
    }

    /**
     * Remove trasitionlistener boolean.
     *
     * @param trasitionListener if null,remove all
     * @return boolean boolean
     */
    public boolean removeTrasitionlistener(TrasitionListener trasitionListener) {
        if (trasitionListener != null) {
            return trasitionListeners.remove(trasitionListener);
        } else {
            trasitionListeners.clear();
            return true;
        }
    }

    /**
     * Gets on focus change listener.
     *
     * @return the on focus change listener
     */
    public OnFocusChangeListener getOnFocusChangeListener() {
        return onFocusChangeListener;
    }

    /**
     * Sets on focus change listener.
     *
     * @param onFocusChangeListener the on focus change listener
     */
    public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
        this.onFocusChangeListener = onFocusChangeListener;
    }

    /**
     * The type Builder.
     */
    public static class Builder {

        /**
         * The Max layer count.
         */
        int maxLayerCount;
        @FocusOrientation
        private int focusOrientation;
        private float layerPadding;
        private float normalViewGap;
        private boolean isAutoSelect;
        private List<TrasitionListener> trasitionListeners;
        private OnFocusChangeListener onFocusChangeListener;
        private long autoSelectMinDuration;
        private long autoSelectMaxDuration;
        private TrasitionListener defaultTrasitionListener;

        /**
         * Instantiates a new Builder.
         */
        public Builder() {
            maxLayerCount = 3;
            focusOrientation = FOCUS_LEFT;
            layerPadding = 60;
            normalViewGap = 60;
            isAutoSelect = true;
            trasitionListeners = new ArrayList<>();
            defaultTrasitionListener = new TrasitionListenerConvert(new SimpleTrasitionListener() {
            });
            trasitionListeners.add(defaultTrasitionListener);
            onFocusChangeListener = null;
            autoSelectMinDuration = 100;
            autoSelectMaxDuration = 300;
        }

        /**
         * Maximum stackable level
         *
         * @param maxLayerCount the max layer count
         * @return the builder
         */
        public Builder maxLayerCount(int maxLayerCount) {
            if (maxLayerCount <= 0) {
                throw new RuntimeException("maxLayerCount The value cannot be less than 0.");
            }
            this.maxLayerCount = maxLayerCount;
            return this;
        }

        /**
         * Stack direction.
         * If the scrolling direction is horizontal, pass {@link #FOCUS_LEFT} or {@link #//FOCUS_Right}.
         * If the scrolling direction is vertical, pass {@link #FOCUS_TOP} or {@link #FOCUS_BOTTOM}.
         *
         * @param focusOrientation the focus orientation
         * @return builder builder
         */
        public Builder focusOrientation(@FocusOrientation int focusOrientation) {
            this.focusOrientation = focusOrientation;
            return this;
        }

        /**
         * Offset between stacked views
         *
         * @param layerPadding the layer padding
         * @return builder builder
         */
        public Builder layerPadding(float layerPadding) {
            if (layerPadding < 0) {
                layerPadding = 0;
            }
            this.layerPadding = layerPadding;
            return this;
        }

        /**
         * Margin between common views
         *
         * @param normalViewGap the normal view gap
         * @return the builder
         */
        public Builder normalViewGap(float normalViewGap) {
            this.normalViewGap = normalViewGap;
            return this;
        }

        /**
         * Whether to automatically select
         *
         * @param isAutoSelect the is auto select
         * @return the builder
         */
        public Builder isAutoSelect(boolean isAutoSelect) {
            this.isAutoSelect = isAutoSelect;
            return this;
        }

        /**
         * Auto select duration builder.
         *
         * @param minDuration the min duration
         * @param maxDuration the max duration
         * @return the builder
         */
        public Builder autoSelectDuration(@IntRange(from = 0) long minDuration, @IntRange(from =
                0) long maxDuration) {
            if (minDuration < 0 || maxDuration < 0 || maxDuration < minDuration) {
                throw new RuntimeException("autoSelectDuration Invalid input parameter.");
            }
            this.autoSelectMinDuration = minDuration;
            this.autoSelectMaxDuration = maxDuration;
            return this;
        }

        /**
         * Added the view change listening interface during the scrolling process in advanced customization.
         *
         * @param trasitionListener the trasition listener
         * @return builder builder
         */
        public Builder addTrasitionListener(TrasitionListener trasitionListener) {
            if (trasitionListener != null) {
                this.trasitionListeners.add(trasitionListener);
            }
            return this;
        }

        /**
         * Listening interface for view change during simplified scrolling. will actually be converted to {@link TrasitionListener}
         *
         * @param simpleTrasitionListener if null,remove current
         * @return simple trasition listener
         */
        public Builder setSimpleTrasitionListener(SimpleTrasitionListener simpleTrasitionListener) {
            this.trasitionListeners.remove(defaultTrasitionListener);
            defaultTrasitionListener = null;
            if (simpleTrasitionListener != null) {
                defaultTrasitionListener = new TrasitionListenerConvert(simpleTrasitionListener);
                this.trasitionListeners.add(defaultTrasitionListener);
            }
            return this;
        }

        /**
         * Sets on focus change listener.
         *
         * @param onFocusChangeListener the on focus change listener
         * @return the on focus change listener
         */
        public Builder setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
            this.onFocusChangeListener = onFocusChangeListener;
            return this;
        }

        /**
         * Build focus layout manager.
         *
         * @return the focus layout manager
         */
        public FocusLayoutManager build() {
            return new FocusLayoutManager(this);
        }
    }


    /**
     * Listening interface for view change during scrolling. It belongs to advanced customization and exposes a lot of key layout data. If customization requirements are not high, use {@link SimpleTrasitionListener}.
     */
    public interface TrasitionListener {

        /**
         * Processes views in a stack.
         *
         * @param focusLayoutManager the focus layout manager
         * @param view               View object. Perform operations on the view only within the method body. Do not forcibly reference it externally. The view needs to be recycled and reused.
         * @param viewLayer          Current level. 0 indicates the bottom layer, and maxLayerCount-1 indicates the top layer.
         * @param maxLayerCount      Maximum Level
         * @param position           Position of an item
         * @param fraction           "一Percentage of progress in the next complete focus slide. The percentage increases in the direction of movement toward the stack (i.e., if Focal_LEFT                           , move the fraction from 0% to 100% from right to left)
         * @param offset             Current sliding offset
         */
        void handleLayerView(FocusLayoutManager focusLayoutManager, Component view, int viewLayer,
                             int maxLayerCount, int position, float fraction, float offset);

        /**
         * Process the view that is in focus (i.e., the view when rolling from the normal position to the focus position, that is, the top-level view of the stack)
         *
         * @param focusLayoutManager the focus layout manager
         * @param view               View object. Perform operations on the view only within the method body. Do not forcibly reference it externally. The view needs to be recycled and reused.
         * @param position           Position of an item
         * @param fraction           Percentage of progress for a complete focus slide. The percentage increases in the direction of movement toward the stack (i.e., if Focal_LEFT                           , move the fraction from 0% to 100% from right to left)
         * @param offset             Current sliding offset
         */
        void handleFocusingView(FocusLayoutManager focusLayoutManager, Component view, int position,
                                float fraction, float offset);

        /**
         * Processes normal views that are not in the stack (except the one that is being focused).
         *
         * @param focusLayoutManager the focus layout manager
         * @param view               View object. Perform operations on the view only within the method body. Do not forcibly reference it externally. The view needs to be recycled and reused.
         * @param position           Position of an item
         * @param fraction           "一Percentage of progress in the next complete focus slide. The percentage increases in the direction of movement toward the stack (i.e., if Focal_LEFT                           , move the fraction from 0% to 100% from right to left)
         * @param offset             Current sliding offset
         */
        void handleNormalView(FocusLayoutManager focusLayoutManager, Component view, int position,
                              float fraction, float offset);

    }

    /**
     * Listening interface for view change during simplified scrolling.
     */
    public static abstract class SimpleTrasitionListener {

        /**
         * Returns the maximum transparency of the stacked view.
         *
         * @param maxLayerCount Maximum Level
         * @return layer view max alpha
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getLayerViewMaxAlpha(int maxLayerCount) {
            return getFocusingViewMaxAlpha();
        }

        /**
         * Returns the minimum transparency of the stacked view.
         *
         * @param maxLayerCount Maximum Level
         * @return layer view min alpha
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getLayerViewMinAlpha(int maxLayerCount) {
            return 0;
        }


        /**
         * Returns the maximum zoom ratio of the stacked view.
         *
         * @param maxLayerCount Maximum Level
         * @return layer view max scale
         */
        public float getLayerViewMaxScale(int maxLayerCount) {
            return getFocusingViewMaxScale();
        }

        /**
         * Returns the minimum scale of the stacked view.
         *
         * @param maxLayerCount Maximum Level
         * @return layer view min scale
         */
        public float getLayerViewMinScale(int maxLayerCount) {
            return 0.7f;
        }


        /**
         * Returns a percentage value within which the view completes a gradual change in zoom and transparency during a complete focus slide.
         * For example, if the return value is 1, the view scales and transparency changes linearly and evenly during a complete focus slide.
         * For example, if the return value is 0.5, it indicates that the focus is within half the distance of a complete focus slide. (The actual logic determines when to start.) , the zoom and transparency of the view will be changed.
         *
         * @return layer change range percent
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getLayerChangeRangePercent() {
            return 0.35f;
        }

        /**
         * Returns the maximum transparency of the focused view.
         *
         * @return focusing view max alpha
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getFocusingViewMaxAlpha() {
            return 1f;
        }

        /**
         * Returns the minimum transparency of the focused view.
         *
         * @return focusing view min alpha
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getFocusingViewMinAlpha() {
            return getNormalViewAlpha();
        }

        /**
         * Returns the maximum zoom ratio of the focused view.
         *
         * @return focusing view max scale
         */
        public float getFocusingViewMaxScale() {
            return 1.2f;
        }

        /**
         * Returns the minimum zoom ratio of the focused view.
         *
         * @return focusing view min scale
         */
        public float getFocusingViewMinScale() {
            return getNormalViewScale();
        }

        /**
         * Description of Return Values{@link #getLayerChangeRangePercent()}
         *
         * @return focusing view change range percent
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getFocusingViewChangeRangePercent() {
            return 0.5f;
        }

        /**
         * Returns the transparency of a common view.
         *
         * @return normal view alpha
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getNormalViewAlpha() {
            return 1.0f;
        }

        /**
         * Returns the scale of a common view.
         *
         * @return normal view scale
         */
        public float getNormalViewScale() {
            return 1.0f;
        }

    }

    /**
     * Converter that converts SimpleTrasitionListener to the actual TrasitionListener
     */
    public static class TrasitionListenerConvert implements TrasitionListener {
        /**
         * The Stl.
         */
        SimpleTrasitionListener stl;

        /**
         * Instantiates a new Trasition listener convert.
         *
         * @param simpleTrasitionListener the simple trasition listener
         */
        public TrasitionListenerConvert(SimpleTrasitionListener simpleTrasitionListener) {
            stl = simpleTrasitionListener;
        }

        @Override
        public void handleLayerView(FocusLayoutManager focusLayoutManager, Component view,
                                    int viewLayer, int maxLayerCount, int position,
                                    float fraction, float offset) {
            /**
             * Expected effect: from 0% to {@link SimpleTrasitionListener#getLayerChangeRangePercent()}
             * The view completes the gradient evenly and remains unchanged thereafter.
             */
            //Convert to True Gradient Change Percentage
            float realFraction;
            if (fraction <= stl.getLayerChangeRangePercent()) {
                realFraction = fraction / stl.getLayerChangeRangePercent();
            } else {
                realFraction = 1.0f;
            }

            float minScale = stl.getLayerViewMinScale(maxLayerCount);
            float maxScale = stl.getLayerViewMaxScale(maxLayerCount);
            float scaleDelta = maxScale - minScale; //Total Zoom Difference
            float currentLayerMaxScale =
                    minScale + scaleDelta * (viewLayer + 1) / (maxLayerCount * 1.0f);
            float currentLayerMinScale = minScale + scaleDelta * viewLayer / (maxLayerCount * 1.0f);
            float realScale =
                    currentLayerMaxScale - (currentLayerMaxScale - currentLayerMinScale) * realFraction;

            float minAlpha = stl.getLayerViewMinAlpha(maxLayerCount);
            float maxAlpha = stl.getLayerViewMaxAlpha(maxLayerCount);
            float alphaDelta = maxAlpha - minAlpha; //Total Transparency Difference
            float currentLayerMaxAlpha =
                    minAlpha + alphaDelta * (viewLayer + 1) / (maxLayerCount * 1.0f);
            float currentLayerMinAlpha = minAlpha + alphaDelta * viewLayer / (maxLayerCount * 1.0f);
            float realAlpha =
                    currentLayerMaxAlpha - (currentLayerMaxAlpha - currentLayerMinAlpha) * realFraction;

            view.setScaleX(realScale);
            view.setScaleY(realScale);
            view.setAlpha(realAlpha);

        }

        @Override
        public void handleFocusingView(FocusLayoutManager focusLayoutManager, Component view,
                                       int position, float fraction, float offset) {
            /**
             * Expected effect: from 0% to {@link SimpleTrasitionListener#getFocusingViewChangeRangePercent()}
             * The view completes the gradient evenly and remains unchanged thereafter.
             */
            //Convert to True Gradient Percentage
            float realFraction;
            if (fraction <= stl.getFocusingViewChangeRangePercent()) {
                realFraction = fraction / stl.getFocusingViewChangeRangePercent();
            } else {
                realFraction = 1.0f;
            }

            float realScale =
                    stl.getFocusingViewMinScale() + (stl.getFocusingViewMaxScale() - stl.getFocusingViewMinScale()) * realFraction;
            float realAlpha =
                    stl.getFocusingViewMinAlpha() + (stl.getFocusingViewMaxAlpha() - stl.getFocusingViewMinAlpha()) * realFraction;

            view.setScaleX(realScale);
            view.setScaleY(realScale);
            view.setAlpha(realAlpha);
        }

        @Override
        public void handleNormalView(FocusLayoutManager focusLayoutManager, Component view,
                                     int position, float fraction, float offset) {
            /**
             * Desired effect: Direct transformation
             */
            view.setScaleX(stl.getNormalViewScale());
            view.setScaleY(stl.getNormalViewScale());
            view.setAlpha(stl.getNormalViewAlpha());
        }
    }

    /**
     * The interface On focus change listener.
     */
    public interface OnFocusChangeListener {
        /**
         * On focus changed.
         *
         * @param focusdPosition      the focusd position
         * @param lastFocusedPosition It may be-1
         */
        void onFocusChanged(int focusdPosition, int lastFocusedPosition);
    }

    /**
     * Log.
     *
     * @param tag the tag
     * @param msg the msg
     */
    public static void log(String tag, String msg) {
        boolean isDebug = true;
        if (isDebug) {
            LogUtil.debug(tag, msg);
        }
    }
}
